#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle("RealTimeIP");

    // Combobox
    ui->comboBox->addItem("Choose a function..."); //0
    ui->comboBox->addItem("Original image"); // 1
    ui->comboBox->addItem("Video stream"); // 2
    ui->comboBox->addItem("Hand contour detection"); // 3
    ui->comboBox->addItem("Apply image processing"); // 4

    //cameraBusy state
    cameraBusy = false;

    /* Turn off full screen
    this->showFullScreen();
    ui->graphicsView->resize(1000, 690);*/

}

////////////////////////////////////////
/// \brief MainWindow::~MainWindow
///
MainWindow::~MainWindow()
{
    delete ui;
}

//************************************OWN FUNCTIONS FROM HERE********************************************************//

////////////////////////////////////////
/// \brief MainWindow::handContourDetector
///
void MainWindow::handContourDetector(Mat matImg)
{
    // Detect skin first
    /*
    Mat skinImg = GetSkin(origImg);
    Mat skinBinImg = skinImg > 10;
    Mat skinBlurImg;
    GaussianBlur(skinImg, skinBlurImg, cv::Size(21, 21), 0);
    Mat contourImg = contourDetector(skinBlurImg);
    */
    Mat skinImg = GetSkin(origImg);
    GaussianBlur(skinImg, skinImg, cv::Size(13, 13), 0);
    Mat skinBinImg = skinImg > 10;
    GaussianBlur(skinBinImg, skinBinImg, cv::Size(21, 21), 0);
    Mat contourImg = contourDetector(skinBinImg);
    namedWindow("Test", WINDOW_NORMAL);
    imshow("Test", contourImg);

    IplImage  *thrImg, *tmpImg1, *tmpImg3;
    CvSeq		*contour = NULL;	/* Hand contour */
    CvSeq		*hull;		/* Hand convex hull */

    CvPoint		hand_center, *fingers;

    CvMemStorage	*hull_st;
    CvMemStorage	*contour_st;
    CvMemStorage	*temp_st;
    CvMemStorage	*defects_st;

    IplConvKernel	*kernel;	/* Kernel for morph operations */

    int		num_fingers;
    int		hand_radius;
    int		num_defects;

    double area, max_area = 0.0;
    CvSeq *contours, *tmp, *defect;

    IplImage *inputImg = new IplImage(skinBinImg);

    thrImg = cvCreateImage(cvGetSize(inputImg), 8, 1);
    tmpImg1 = cvCreateImage(cvGetSize(inputImg), 8, 1);
    tmpImg3 = cvCreateImage(cvGetSize(inputImg), 8, 3);
    kernel = cvCreateStructuringElementEx(9, 9, 4, 4, CV_SHAPE_RECT, NULL);
    contour_st = cvCreateMemStorage(0);
    hull_st = cvCreateMemStorage(0);
    temp_st = cvCreateMemStorage(0);

    /* Soften image */
    cvSmooth(inputImg, tmpImg3, CV_GAUSSIAN, 15, 15, 0, 0);
    /* Remove some impulsive noise */
    cvSmooth(tmpImg3, tmpImg3, CV_MEDIAN, 15, 15, 0, 0);

    cvCvtColor(tmpImg3, tmpImg3, CV_BGR2HSV);

    /*
     * Apply threshold on HSV values
     * Threshold values should be customized according to environment
     */
    cvInRangeS(tmpImg3,  cvScalar(0, 0, 160, 0), cvScalar(255, 400, 300, 255), thrImg);

    /* Apply morphological opening */
    //cvMorphologyEx(thrImg, thrImg, NULL, kernel,   CV_MOP_OPEN, 1);
    //cvSmooth(thrImg, thrImg, CV_GAUSSIAN, 3, 3, 0, 0);

    /* cvFindContours modifies input image, so make a copy */
    cvCopy(thrImg, tmpImg1, NULL);
    cvFindContours(tmpImg1, temp_st, &contours,  sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
    //cvFindContours(tmpImg1, temp_st, &contours,  sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_L1, cvPoint(0, 0));

    // Select contour having greatest area
    for (tmp = contours; tmp; tmp = tmp->h_next) {
        area = fabs(cvContourArea(tmp, CV_WHOLE_SEQ, 0));
        if (area > max_area) {
            max_area = area;
            contour = tmp;
        }
    }

    /* Approximate contour with poly-line*/
    if (contour) {
        contour = cvApproxPoly(contour, sizeof(CvContour), contour_st, CV_POLY_APPROX_DP, 30,  1);
        contour = contour;
    }

    hull = cvConvexHull2(contour, hull_st, CV_CLOCKWISE);
    //hull = cvConvexHull2(contour, 0, CV_CLOCKWISE, 0);
    defect = cvConvexityDefects(contour, hull, NULL);

    cvDrawContours(inputImg, contour, CV_RGB(155,155,155), CV_RGB(155,155,155), 0, 10, CV_AA, cvPoint(0,0));

    for(; defect; defect = defect->h_next)
    {
        int nomdef = defect->total;
        if(!nomdef)
            continue;
        Point textCoord;
        CvFont font;
        cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, 3,3,0,5);
        CvConvexityDefect* defectArray = (CvConvexityDefect*)malloc(sizeof(CvConvexityDefect)*nomdef);
        cvCvtSeqToArray(defect, defectArray, CV_WHOLE_SEQ);
        for(int i = 0; i < nomdef; i++)
        {
            textCoord.x = defectArray[i].depth_point->x;
            textCoord.y = defectArray[i].depth_point->y;

            //cvCircle(inputImg, *(defectArray[i].end), 30, CV_RGB(255, 0, 0), -1, 8, 0);
            cvCircle(inputImg, *(defectArray[i].start), 30, CV_RGB(0, 255, 0), -1, 8, 0);
            //cvCircle(inputImg, *(defectArray[i].depth_point), 30, CV_RGB(0, 0, 255), -1, 8, 0);
            cvPutText(inputImg, QString::number(i).toStdString().c_str(), textCoord,  &font, CV_RGB(0, 155, 255));
        }
        free(defectArray);
    }

    Mat tmpImg = cvarrToMat(inputImg);
    displayMatImage(tmpImg);

    /*
    cvReleaseMemStorage( &contour_st );
    cvReleaseMemStorage( &hull_st );
    cvReleaseMemStorage( &temp_st );
    cvReleaseImage(&thrImg);
    cvReleaseImage(&tmpImg1);
    cvReleaseImage(&tmpImg3);
    cvReleaseImage(&inputImg);*/


}

////////////////////////////////////////
/// \brief MainWindow::contourDetector
/// \param matImg
///
Mat MainWindow::contourDetector(Mat matImg)
{
    Mat canny_output, matGrayImg;
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;
    int thresh = 10;
    RNG rng(12345);

    /// Convert image to gray and blur it
    ui->sttLabel->setText("Status: Converting and bluring...");
    cvtColor( matImg, matGrayImg, CV_BGR2GRAY );
    blur( matGrayImg, matGrayImg, Size(3,3) );

    ui->sttLabel->setText("Status: Edge detecting...");
    Canny( matGrayImg, canny_output, thresh, thresh*2, 3 );

    /// Find contours
    ui->sttLabel->setText("Status: Finding contour...");
    findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

    /// Draw contours
    ui->sttLabel->setText("Status: Drawing contour...");
    Mat contourImg = Mat::zeros( canny_output.size(), CV_8UC3 );
    for( int i = 0; i< contours.size(); i++ )
       {
         //Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
        Scalar color = Scalar( 0, 255, 0 );
         drawContours( contourImg, contours, i, color, 2, 8, hierarchy, 0, Point() );
       }
    return contourImg;
}

////////////////////////////////////////
/// \brief MainWindow::on_loadBtn_clicked
/// Load original image
void MainWindow::on_loadBtn_clicked()
{
    ui->sttLabel->setText("Status: Load image into graphic view!");

    // Open image file
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open Image"), QDir::currentPath());

    if (!fileName.isEmpty()) {

        origImg = imread(fileName.toStdString());

        if (origImg.empty()) {
                //QMessageBox::information(this, tr("Load Warning"), tr("Cannot load %1.").arg(fileName));
                ui->sttLabel->setText("Status: Unable to load image!");
                return;
         }

         displayMatImage(origImg);
    }

}

void MainWindow::activeIPAlgo()
{

}


////////////////////////////////////////
/// \brief MainWindow::activeWCVideoStream
///// Read video stream from webcam
void MainWindow::activeWCVideoStream()
{
     ui->sttLabel->setText("Status: Your own function is be tested!");


    VideoCapture cap(0); // open the default camera
    //change cameraBusyinue state
    if(cameraBusy == false)
        cameraBusy = true;//start cap(0)
    else
        cameraBusy = false;//stop cap(0)

    if(!cap.isOpened() && cameraBusy!=false)  // check if we succeeded
        ui->sttLabel->setText("Status: Error to open cam!");    

    for(;;)
    {
        if(cameraBusy == false)//stop cap(0)
        {
            cap.release();
            ui->sttLabel->setText("Click function button");
            break;
        }

        Mat frame;
        cap >> frame; // get a new frame from camera

        // Display
        displayMatImage(frame);

        if(waitKey(30) >= 0) break;        
    }
    // the camera will be deinitialized automatically in VideoCapture destructor

}

////////////////////////////////////////
/// \brief MainWindow::on_comboBox_currentIndexChanged
/// \param index
///
void MainWindow::on_comboBox_currentIndexChanged(int index)
{
    ui->sttLabel->setText("Status: " + ui->comboBox->currentText());
    switch (index)
    {
        case 0:
            ui->sttLabel->setText("Status: " + ui->comboBox->currentText());
            break;

        case 1: // original image
            ui->sttLabel->setText("Status: " + ui->comboBox->currentText());
            displayMatImage(origImg);
            break;

        case 2: // Video stream
            ui->sttLabel->setText("Status: " + ui->comboBox->currentText());
            activeWCVideoStream();
            break;

        case 3:
            ui->sttLabel->setText("Status: " + ui->comboBox->currentText());
            handContourDetector(origImg);
            break;
    }
}


////////////////////// FUNCTIONS FOR GETTING HAND SKIN /////////////////////////////////////////
/// \brief MainWindow::R1
/// \param R
/// \param G
/// \param B
/// \return

bool MainWindow::R1(int R, int G, int B) {
    bool e1 = (R>95) && (G>40) && (B>20) && ((max(R,max(G,B)) - min(R, min(G,B)))>15) && (abs(R-G)>15) && (R>G) && (R>B);
    bool e2 = (R>220) && (G>210) && (B>170) && (abs(R-G)<=15) && (R>B) && (G>B);
    return (e1||e2);
}


///////////////////////////////////////
/// \brief MainWindow::R2
/// \param Y
/// \param Cr
/// \param Cb
/// \return
///
bool MainWindow::R2(float Y, float Cr, float Cb) {
    bool e3 = Cr <= 1.5862*Cb+20;
    bool e4 = Cr >= 0.3448*Cb+76.2069;
    bool e5 = Cr >= -4.5652*Cb+234.5652;
    bool e6 = Cr <= -1.15*Cb+301.75;
    bool e7 = Cr <= -2.2857*Cb+432.85;
    return e3 && e4 && e5 && e6 && e7;
}

//////////////////////////////////////////
/// \brief MainWindow::R3
/// \param H
/// \param S
/// \param V
/// \return
///
bool MainWindow::R3(float H, float S, float V) {
    return (H<25) || (H > 230);
}

////////////////////////////////////////
/// \brief MainWindow::GetSkin
/// \param src
/// \return
///
Mat MainWindow::GetSkin(Mat const &src) {
    // allocate the result matrix
    Mat dst = src.clone();

    Vec3b cwhite = Vec3b::all(255);
    Vec3b cblack = Vec3b::all(0);

    Mat src_ycrcb, src_hsv;
    // OpenCV scales the YCrCb components, so that they
    // cover the whole value range of [0,255], so there's
    // no need to scale the values:
    cvtColor(src, src_ycrcb, CV_BGR2YCrCb);
    // OpenCV scales the Hue Channel to [0,180] for
    // 8bit images, so make sure we are operating on
    // the full spectrum from [0,360] by using floating
    // point precision:
    src.convertTo(src_hsv, CV_32FC3);
    cvtColor(src_hsv, src_hsv, CV_BGR2HSV);
    // Now scale the values between [0,255]:
    normalize(src_hsv, src_hsv, 0.0, 255.0, NORM_MINMAX, CV_32FC3);

    for(int i = 0; i < src.rows; i++) {
        for(int j = 0; j < src.cols; j++) {

            Vec3b pix_bgr = src.ptr<Vec3b>(i)[j];
            int B = pix_bgr.val[0];
            int G = pix_bgr.val[1];
            int R = pix_bgr.val[2];
            // apply rgb rule
            bool a = R1(R,G,B);

            Vec3b pix_ycrcb = src_ycrcb.ptr<Vec3b>(i)[j];
            int Y = pix_ycrcb.val[0];
            int Cr = pix_ycrcb.val[1];
            int Cb = pix_ycrcb.val[2];
            // apply ycrcb rule
            bool b = R2(Y,Cr,Cb);

            Vec3f pix_hsv = src_hsv.ptr<Vec3f>(i)[j];
            float H = pix_hsv.val[0];
            float S = pix_hsv.val[1];
            float V = pix_hsv.val[2];
            // apply hsv rule
            bool c = R3(H,S,V);

            if(!(a&&b&&c))
                dst.ptr<Vec3b>(i)[j] = cblack;
        }
    }
    return dst;
}



/* FUNCTIONS FOR DISPLAYING QIMAGE AND MAT IMAGE ON GRAPHIC VIEW */
////////////////////////////////////////
/// \brief MainWindow::displayQImage
/// \param qImage
///
void MainWindow::displayQImage(QImage qImage)
{
    //QImage image = qImage.convertToFormat(QImage::Format_RGB32);
    //QImage image = qImage.convertToFormat(QImage::Format_Indexed8);
    QPixmap pixmap = QPixmap::fromImage(qImage);
    QGraphicsScene *viewScene = new QGraphicsScene(0, 0, qImage.width(), qImage.height(), this);
    QGraphicsPixmapItem *item = viewScene->addPixmap(pixmap.scaled(QSize(qImage.width(), qImage.height()),
                                                                   Qt::KeepAspectRatio,
                                                                   Qt::SmoothTransformation));
    // Display image
    ui->graphicsView->setAlignment(Qt::AlignCenter);
    ui->graphicsView->fitInView(QRectF(0, 0, qImage.width(), qImage.height()), Qt::KeepAspectRatio);
    ui->graphicsView->setScene(viewScene);
    ui->graphicsView->show();
}

////////////////////////////////////////
/// \brief MainWindow::displayMatImage
/// \param matImage
///
void MainWindow::displayMatImage(Mat matImage)
{
    Mat tmpImage;

    // Convert color space
    cvtColor(matImage, tmpImage, CV_BGR2RGB);

    QImage qimage((const uchar*) tmpImage.data, tmpImage.cols,
                  tmpImage.rows, tmpImage.step,  QImage::Format_RGB888);

    displayQImage(qimage.scaled(ui->graphicsView->width(),
                                 ui->graphicsView->height(),
                                 Qt::IgnoreAspectRatio));
}

////////////////////////////////////////
/// \brief MainWindow::displayIplImage
/// \param matImage
///
void MainWindow::displayIplImage(IplImage *iplimage_)
{
    IplImage *iplimage;

    // Convert color space
    cvCvtColor(iplimage_, iplimage, CV_BGR2RGB);

    QImage qimage((const uchar*) iplimage->imageData,
                  iplimage->width,
                  iplimage->height,
                  QImage::Format_RGB888);

    displayQImage(qimage.scaled(ui->graphicsView->width(),
                                 ui->graphicsView->height(),
                                 Qt::IgnoreAspectRatio));
}
